﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace pictureAnalyse
{
    class PicTool
    {
        static String ToolsName = "pictureAnalyse";
        static Color C0 = Color.FromArgb(0, 0, 0, 0);      // 透明像素

        # region 获取图像颜色值信息、保存、复制
        // ------------------------------------------------------

        /// <summary>
        /// 获取i,j坐标处的颜色均值，默认值（0,0,0,0）
        /// </summary>
        private static Color getPixelColor(Bitmap pic, int i, int j)
        {
            if (i < 0 || j < 0 || i >= pic.Height || j >= pic.Width) return C0;
            else return pic.GetPixel(j, i);
        }

        /// <summary>
        /// 获取P坐标处的颜色均值，默认值（0,0,0,0）
        /// </summary>
        private static Color getPixelColor(Bitmap pic, Point P)
        {
            if (P.x < 0 || P.y < 0 || P.x >= pic.Height || P.y >= pic.Width) return C0;
            else return pic.GetPixel(P.y, P.x);
        }

        /// <summary>
        /// 设置i,j坐标处的颜色均值，默认值（0,0,0,0）
        /// </summary>
        private static void setPixelColor(Bitmap pic, int i, int j, Color C)
        {
            if (C == null) C = Color.FromArgb(0, 0, 0, 0);
            if (i < 0 || j < 0 || i >= pic.Height || j >= pic.Width) return ;
            else pic.SetPixel(j, i, C);
        }

        /// <summary>
        /// 设置i,j坐标处的颜色均值，默认值（0,0,0,0）
        /// </summary>
        private static void setPixelColor(Bitmap pic, Point P, Color C)
        {
            if (C == null) C = Color.FromArgb(0, 0, 0, 0);
            if (P.x < 0 || P.y < 0 || P.x >= pic.Height || P.y >= pic.Width) return;
            else pic.SetPixel(P.y, P.x, C);
        }

        /// <summary>
        /// 判断坐标处是否存在非透明像素值
        /// </summary>
        public static bool Exist(Bitmap pic, int x, int y)
        {
            Color C = getPixelColor(pic, x, y);
            return C.A != 0;
        }

        /// <summary>
        /// 判断坐标P处是否存在非透明像素值
        /// </summary>
        public static bool Exist(Bitmap pic, Point P)
        {
            Color C = getPixelColor(pic, P.x, P.y);
            return C.A != 0;
        }

        /// <summary>
        /// 获取图像中的第一个非透明像素坐标
        /// </summary>
        private static Point getFistPoint(Bitmap pic)
        {
            for (int j = 0; j < pic.Width; j++)         // 列遍历
            {
                for (int i = 0; i < pic.Height; i++)    // 行遍历
                {
                    if (Exist(pic, i, j))
                    {
                        return new Point(i, j);
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Image转化为Bitmap
        /// </summary>
        public static Bitmap Copy(Image pic)
        {
            //创建图像  
            Bitmap tmp = new Bitmap(pic.Width, pic.Height);                //按指定大小创建位图  
            Rectangle Rect = new Rectangle(0, 0, pic.Width, pic.Height);   //pic的整个区域  

            //绘制  
            Graphics g = Graphics.FromImage(tmp);                   //从位图创建Graphics对象  
            g.Clear(Color.FromArgb(0, 0, 0, 0));                    //清空  

            g.DrawImage(pic, Rect, Rect, GraphicsUnit.Pixel);       //从pic的给定区域进行绘制  

            return tmp;     //返回构建的新图像
        }


        //=======================================================
        //图像的保存
        //=======================================================

        /// <summary>
        /// 保存图像pic到默认目录中，保存名称为name
        /// </summary>
        public static String SaveToFile(Image pic, String name="")
        {
            if (name.Equals("")) name = DateTime.Now.Ticks.ToString() + ".png";
            string CurDir = System.AppDomain.CurrentDomain.BaseDirectory + DateTime.Now.Date.ToString("yyyy_MM_dd") + "(" + ToolsName + @")导出\";         //设置当前目录
            if (!System.IO.Directory.Exists(CurDir)) System.IO.Directory.CreateDirectory(CurDir);   //该路径不存在时，在当前文件目录下创建文件夹"导出.."

            SaveToFile(pic, CurDir + name, true);    //已替换方式保存图像
            return CurDir + name;
        }

        //保存图像pic到文件fileName中
        public static void SaveToFile(Image pic, string fileName, bool replace)
        {
            SaveToFile(pic, fileName, replace, null);
        }
        //保存图像pic到文件fileName中，指定图像保存格式
        public static void SaveToFile(Image pic, string fileName, bool replace, ImageFormat format)    //ImageFormat.Jpeg
        {
            //若图像已存在，则删除
            if (System.IO.File.Exists(fileName) && replace)
                System.IO.File.Delete(fileName);

            //若不存在则创建
            if (!System.IO.File.Exists(fileName))
            {
                if (format == null) format = getFormat(fileName);   //根据拓展名获取图像的对应存储类型

                if (format == ImageFormat.MemoryBmp) pic.Save(fileName);
                else pic.Save(fileName, format);                    //按给定格式保存图像
            }
        }

        //根据文件拓展名，获取对应的存储类型
        public static ImageFormat getFormat(string filePath)
        {
            ImageFormat format = ImageFormat.MemoryBmp;
            String Ext = System.IO.Path.GetExtension(filePath).ToLower();

            if (Ext.Equals(".png")) format = ImageFormat.Png;
            else if (Ext.Equals(".jpg") || Ext.Equals(".jpeg")) format = ImageFormat.Jpeg;
            else if (Ext.Equals(".bmp")) format = ImageFormat.Bmp;
            else if (Ext.Equals(".gif")) format = ImageFormat.Gif;
            else if (Ext.Equals(".ico")) format = ImageFormat.Icon;
            else if (Ext.Equals(".emf")) format = ImageFormat.Emf;
            else if (Ext.Equals(".exif")) format = ImageFormat.Exif;
            else if (Ext.Equals(".tiff")) format = ImageFormat.Tiff;
            else if (Ext.Equals(".wmf")) format = ImageFormat.Wmf;
            else if (Ext.Equals(".memorybmp")) format = ImageFormat.MemoryBmp;

            return format;
        }

        //=======================================================
        // 提示信息
        //=======================================================

        /// <summary>
        /// 显示提示信息str,并选择是否打开文件目录Dir
        /// </summary>
        public static void MessageWithOpen(String str, String Dir)
        {
            bool ok = (MessageBox.Show(str, "打开？", MessageBoxButtons.OKCancel) == DialogResult.OK);
            if (ok) System.Diagnostics.Process.Start("explorer.exe", "/e,/select, " + Dir);
        }

        // ------------------------------------------------------
        # endregion


        # region png图像矩形裁切，裁切掉图像周边多余的空像素区域
        // ------------------------------------------------------

        /// <summary>
        /// 从图像pic中截取最小矩形颜色区域构建新的图像
        /// </summary>
        public static Bitmap GetMinRect(Bitmap pic)
        {
            Rectangle minRect = GetMiniRect(pic);
            return GetRect(pic, minRect);
        }

        private Rectangle GetMiniLeftRect(Bitmap pic)
        { return GetMiniLeftRect(pic, 0); }

        /// <summary>
        /// 从图像pic中截取区域Rect构建新的图像
        /// </summary>
        private static Bitmap GetRect(Image pic, Rectangle Rect)
        {
            //创建图像
            Rectangle drawRect = new Rectangle(0, 0, Rect.Width, Rect.Height);  //绘制整块区域
            Bitmap tmp = new Bitmap(drawRect.Width, drawRect.Height);           //按指定大小创建位图

            //绘制
            Graphics g = Graphics.FromImage(tmp);                   //从位图创建Graphics对象
            g.Clear(Color.FromArgb(0, 0, 0, 0));                    //清空
            g.DrawImage(pic, drawRect, Rect, GraphicsUnit.Pixel);   //从pic的给定区域进行绘制

            return tmp;     //返回构建的新图像
        }

        /// <summary>
        /// 获取图像pic的最小非透明像素矩形区域大小，自动剔除边缘透明区域
        /// 原理： 记录所有非透明像素点的最小和最大坐标
        /// </summary>
        public static Rectangle GetMiniRect(Bitmap pic, int Trans=0)
        {
            Point P = new Point(-1, -1), Q = new Point(-1, -1); // 分别代表起点和终点

            for (int i = 0; i < pic.Height; i++)        //行遍历
            {
                for (int j = 0; j < pic.Width; j++)     //列遍历
                {
                    if (P.x != -1 && P.x <= i && i <= Q.x && P.y <= j && j <= Q.y) { j = Q.y; continue; }  //跳过P与Q之间的列遍历
                    if (Exist(pic, i, j))
                    {
                        if (P.x == -1) { P = new Point(i, j); Q = new Point(i, j); }    //首个非透明像素点

                        //最小区域左上点
                        if (P.x > i) P.x = i;           //X记录行位置
                        if (P.y > j) P.y = j;           //Y记录列位置

                        //最小区域右下点
                        if (Q.x < i) Q.x = i;
                        if (Q.y < j) Q.y = j;
                    }
                }
            }

            if (P.x == -1) return new Rectangle(0, 0, 0, 0);
            return new Rectangle(P.y, P.x, Q.y - P.y +1, Q.x - P.x+1);
        }

        /// <summary>
        /// 获取从左上点开始的图像pic的最小非透明像素矩形区域大小，仅剔除右侧或下侧边缘透明区域
        /// 原理： 记录所有非透明像素点的最小和最大坐标
        /// </summary>
        private Rectangle GetMiniLeftRect(Bitmap pic, int Trans)
        {
            Rectangle Rect = GetMiniRect(pic, Trans);  //获取最小非透明像素矩形区域

            Rect.Width += Rect.X;                      //转化为从左上点开始区域，仅剔除右下侧透明区域
            Rect.Height += Rect.Y;
            Rect.X = 0;
            Rect.Y = 0;

            return Rect;
        }

        // ------------------------------------------------------
        # endregion


        # region png图像分解，以透明像素为边缘，按连续像素块，自动分解png大图为若干子图
        // ------------------------------------------------------

        /// <summary>
        /// 判断坐标处是否存在非透明像素值
        /// </summary>
        private static bool ExistP(Bitmap pic, Point P0, List<String> listP)
        {
            String P = P0 == null ? "null" : P0.ToString();
            return (!listP.Contains(P) && Exist(pic, P0));
        }

        /// <summary>
        /// 获取图像pic的最小非透明像素矩形区域大小，自动剔除边缘透明区域
        /// 原理： 记录所有非透明像素点的最小和最大坐标
        /// </summary>
        public static List<Bitmap> GetContinuousRect(Image pic0)
        {
            Bitmap pic = Copy(pic0);                    // 复制一个副本
            List<Bitmap> list = new List<Bitmap>();     // 记录所有图像

            List<String> listP = new List<String>();    // 存储所有连续像素坐标
            Point P = getFistPoint(pic);                // 获取图像中第一个非透明像素
            while (P != null)
            {
                getNearPoints(pic, P, listP);           // 获取从P点开始的所有连续像素信息，至listP中
                Bitmap img = getListPic(pic, listP);    // 获取连续像素的复制生成的图像
                list.Add(img);                          // 记录获取到的图像
                clearPoints(pic, listP);                // 清除listP中所有的像素

                listP.Clear();
                P = getFistPoint(pic);                  // 获取第一个非透明像素
            }

            return list;
        }

        /// <summary>
        /// 从图像from中坐标P0开始，获取周边所有连续像素的坐标信息
        /// </summary>
        private static void getNearPoints(Bitmap from, Point P0, List<String> listP)
        {
            List<Point> list = getAroundPoint(from, P0);  // 获取坐标i,j周边的非透明像素
            list = xor(list, listP);                      // 剔除存在于记录集中的坐标

            foreach (Point P in list) listP.Add(P.ToString());          // 记录当前点周边的坐标信息
            foreach (Point P in list) getNearPoints(from, P, listP);    // 记录周边像素的周边像素信息
        }

        /// <summary>
        /// 获取listp中所有像素的坐标区域
        /// </summary>
        private static Rectangle getRect(List<String> listP)
        {
            Point Q = null, L = null;
            foreach (String S in listP)
            {
                Point P = new Point(S);
                if (Q == null) Q = P.clone();
                if (L == null) L = P.clone();

                //最小区域左上点
                if (Q.x > P.x) Q.x = P.x;           //X记录行位置
                if (Q.y > P.y) Q.y = P.y;           //Y记录列位置

                //最小区域右下点
                if (L.x < P.x) L.x = P.x;
                if (L.y < P.y) L.y = P.y;
            }

            if (Q == null) Q = new Point(0, 0);
            if (L == null) L = new Point(0, 0);

            Rectangle rect = new Rectangle();
            rect = new Rectangle(Q.x, Q.y, L.y - Q.y + 1, L.x - Q.x + 1);
            return rect;
        }

        // 添加P到lsitP中，并记录最小和最大坐标
        private static void recordPoint(Point P0, List<String> listP, ref Point Q, ref Point L)
        {
            String P = P0.ToString();
            if (!listP.Contains(P))
            {
                // 记录坐标信息
                listP.Add(P); 

                //最小区域左上点
                if (Q.x > P0.x) Q.x = P0.x;           //X记录行位置
                if (Q.y > P0.y) Q.y = P0.y;           //Y记录列位置

                //最小区域右下点
                if (L.x < P0.x) L.x = P0.x;
                if (L.y < P0.y) L.y = P0.y;
            }
        }

        /// <summary>
        /// 获取坐标P周边八个点中的非透明像素点坐标
        /// </summary>
        private static List<Point> getAroundPoint(Bitmap pic, Point P)
        {
            List<Point> list = new List<Point>();
            for (int i0 = -1; i0 < 2; i0++)
            {
                for (int j0 = -1; j0 < 2; j0++)
                {
                    Point p = P.Add(i0, j0);
                    if (Exist(pic, p)) list.Add(p);
                }
            }
            return list;
        }

        /// <summary>
        /// 剔除cur中，存在于listP中的所有坐标
        /// </summary>
        private static List<Point> xor(List<Point> cur, List<String> listP)
        {
            List<Point> list = new List<Point>();
            foreach (Point P in cur)
            {
                if(!listP.Contains(P.ToString())) list.Add(P);
            }
            return list;
        }

        /// <summary>
        /// 根据listP中所有像素坐标，复制生成新的图像
        /// </summary>
        private static Bitmap getListPic(Bitmap from, List<String> listP)
        {
            if (listP == null || listP.Count == 0) return null;

            Rectangle rect = getRect(listP);                        // 获取listP中所有坐标点所在的坐标区域

            //创建图像
            Bitmap tmp = new Bitmap(rect.Width, rect.Height);       //按指定大小创建位图
            Graphics g = Graphics.FromImage(tmp);                   //从位图创建Graphics对象
            g.Clear(C0);                                            // 清空

            foreach (String P in listP)
            {
                Point p = new Point(P);
                Color C = getPixelColor(from, p.x, p.y);            // 获取坐标处的像素值
                setPixelColor(tmp, p.x - rect.X, p.y -rect.Y, C);   // 复制像素颜色值到tmp
            }

            return tmp;
        }

        /// <summary>
        /// 根据listP中所有像素坐标，复制生成新的图像
        /// </summary>
        private static void clearPoints(Bitmap from, List<String> listP)
        {
            if (listP == null || listP.Count == 0) return;
            foreach (String P in listP) setPixelColor(from, new Point(P), C0);        // 清除此像素信息
        }

        // ------------------------------------------------------
        #endregion


        # region 获取图像中的闭环数目
        // ------------------------------------------------------


        /// <summary>
        /// 获取图像pic上从坐标P开始方向oren的下一个非空像素坐标。
        /// 优先选择下一个方向的像素点；若没有，则选择当前方向的下一个像素；
        /// 若没有，则选择前一个方向的像素；若没有，则返回空
        /// </summary>
        private static Point getNext(Bitmap pic, Point P, ref Orientation oren)
        {
            Point Next = P.Add(oren.pekNext()); // 获取下一个方向的像素坐标
            if (Exist(pic, Next))               // 若存在下一个像素
            {
                oren.toNext();                  // 移动至下一个方向
                return Next;
            }

            Next = P.Add(oren.Cur());           // 获取当前方向的像素坐标
            if (Exist(pic, Next)) return Next;

            Next = P.Add(oren.pekPre());        // 获取前一个方向的像素坐标
            if (Exist(pic, Next)) return Next;

            return null;    // 返回空
        }

        /// <summary>
        /// 获取图像pic上从坐标P开始方向oren逆时针方向的，前一个非空像素坐标
        /// </summary>
        private static Point getPre(Bitmap pic, Point P, ref Orientation oren)
        {
            Point Next = P.Add(oren.pekPre());  // 获取前一个方向的像素坐标
            if (Exist(pic, Next))               // 若存在下一个像素
            {
                oren.toPre();                   // 移动至前一个方向
                return Next;
            }

            Next = P.Add(oren.Cur());           // 获取当前方向的像素坐标
            if (Exist(pic, Next)) return Next;

            Next = P.Add(oren.pekNext());       // 获取下一个方向的像素坐标
            if (Exist(pic, Next)) return Next;

            return null;    // 返回空
        }

        /// <summary>
        /// 获取图像pic的最小非透明像素矩形区域大小，自动剔除边缘透明区域
        /// 原理： 记录所有非透明像素点的最小和最大坐标
        /// </summary>
        public static int GetCiculesNum(Image pic0)
        {
            int count = 0;
            Bitmap pic = Copy(pic0);                            // 复制一个副本
            List<String> listP = new List<String>();            // 存储所有连续像素坐标
            Point P = getFistPoint(pic);                        // 获取第一个非透明像素
            if(inCircle(pic, P)) return 1;                      // 存在像素圈返回1

            return 0;
        }

        /// <summary>
        /// 判断坐标P是否为pic中像素环上的点
        /// </summary>
        private static bool inCircle(Bitmap pic, Point P)
        {
            List<String> listP = new List<String>();    // 坐标记录列表
            Orientation oren = new Orientation();       // 方向变量
            while (P!=null && Exist(pic, P))            // 若存在像素
            {
                P = getNext(pic, P, ref oren);          // 获取oren方向的下一个像素，并自动变动方向

                if (P == null) return false;                        // 不含有该方向的下一个像素
                else if (listP.Contains(P.ToString())) return true; // 返回到原有像素坐标处，则含有像素圈      
                else listP.Add(P.ToString());                       // 记录当前像素坐标
            }

            return false;
        }




        // ------------------------------------------------------
        # endregion


        //# region png图像分解，以透明像素为边缘，按连续像素块，自动分解png大图为若干子图
        //// ------------------------------------------------------

        ///// <summary>
        ///// 获取图像pic的最小非透明像素矩形区域大小，自动剔除边缘透明区域
        ///// 原理： 记录所有非透明像素点的最小和最大坐标
        ///// </summary>
        //public static List<Bitmap> GetContinuousRect(Image pic0)
        //{
        //    List<Bitmap> list = new List<Bitmap>();

        //    Bitmap pic = Copy(pic0);                    // 复制一个副本
        //    //Graphics g0 = Graphics.FromImage(pic);      // 创建Graphics对象

        //    //创建图像
        //    Bitmap tmp = new Bitmap(pic.Width, pic.Height);         //按指定大小创建位图
        //    Graphics g = Graphics.FromImage(tmp);                   //从位图创建Graphics对象  
            
        //    for (int i = 0; i < pic.Height; i++)        // 行遍历
        //    {
        //        for (int j = 0; j < pic.Width; j++)     // 列遍历
        //        {
        //            if (Exist(pic, i, j))
        //            {
        //                g.Clear(C0);                    // 清空
        //                moveNearPixels(pic, tmp, i, j); // 绘制所有连续像素至tmp
        //                Bitmap min = GetMinRect(tmp);   // 清除周边空白像素
        //                list.Add(min);
        //            }
        //        }
        //    }

        //    return list;
        //}

        // /// <summary>
        ///// 移除图像from中所有与i,j坐标出像素连续的所有像素到图像to中
        ///// </summary>
        //public static void moveNearPixels(Bitmap from, Bitmap to, int i, int j)
        //{
        //    List<Point> list = getAroundPoint(from, i, j);                  // 获取坐标i,j周边的非透明像素
        //    foreach (Point P in list) movePixel(from, to, P.x, P.y);        // 移除现有坐标处的的像素点
        //    foreach (Point P in list) moveNearPixels(from, to, P.x, P.y);   // 移除像素周边的非透明像素
        //}

        ///// <summary>
        ///// 获取坐标i,j周边八个点中的非透明像素点坐标
        ///// </summary>
        //public static List<Point> getAroundPoint(Bitmap pic, int i, int j)
        //{
        //    List<Point> list = new List<Point>();
        //    for (int i0 = -1; i0 < 2; i0++)
        //    {
        //        for (int j0 = -1; j0 < 2; j0++)
        //        {
        //            if (Exist(pic, i + i0, j + j0)) 
        //                list.Add(new Point(i + i0, j + j0));
        //        }
        //    }
        //    return list;
        //}

        ///// <summary>
        ///// 移动图像from中i,j坐标处的像素点至图像to
        ///// </summary>
        //public static void movePixel(Bitmap from, Bitmap to, int i, int j)
        //{
        //    Color C = getPixelColor(from, i, j);    // 获取坐标处的像素值
        //    setPixelColor(to, i, j, C);             // 复制像素颜色值到tmp
        //    setPixelColor(from, i, j, C0);          // 清除缓存图像此点的像素
        //}
        //// ------------------------------------------------------
        //#endregion


        # region 浓缩颜色值
        // ------------------------------------------------------

        /// <summary>
        /// 获取i,j周围的颜色均值
        /// </summary>
        private static Color getAverageColor(Bitmap pic, int i, int j, int num = 1)
        {
            int shrinkNum = (num * 2 + 1) * (num * 2 + 1) / 2 + 1;
            int A = 0, R = 0, G = 0, B = 0, count = 0;
            Color C, C0 = Color.Transparent;
            // i,j周边9个像素值
            for (int m = -num; m <= num; m++)
            {
                for (int n = -num; n <= num; n++)
                {
                    C = getPixelColor(pic, i + m, j + n);
                    if (C.A != 0)   // 若像素值不为空
                    {
                        count++;
                        A += C.A;
                        R += C.R;
                        G += C.G;
                        B += C.B;
                        if (m == 0 && n == 0) C0 = C;
                    }
                }
            }
            if (count == 0 || (num != 1 && C0.A == 0) || (num == 1 && count < shrinkNum)) C = Color.FromArgb(0, 0, 0, 0);
            else C = Color.FromArgb((int)(A / count), (int)(R / count), (int)(G / count), (int)(B / count));

            return C;
        }

        /// <summary>
        /// 对图像中的像素进行浓缩，取周围像素的均值
        /// </summary>
        public static Bitmap shrinkPixels(Bitmap pic, int shrinkNum = 1)
        {
            //创建图像  
            Bitmap tmp = new Bitmap(pic.Width, pic.Height);     //按指定大小创建位图
            Graphics g = Graphics.FromImage(tmp);               //从位图创建Graphics对象

            //绘制
            Color c;
            for (int i = 0; i < pic.Height; i++)
            {
                for (int j = 0; j < pic.Width; j++)
                {
                    c = getAverageColor(pic, i, j, shrinkNum);  // 获取各像素周边的颜色均值
                    tmp.SetPixel(j, i, c);                      // 设置颜色值
                }
            }

            return tmp;     //返回构建的新图像 
        }

        // ------------------------------------------------------
        # endregion


    }
}
